<?php

namespace App\Http\Middleware;

use App\Constant;
use App\Models\Agent;
use Carbon\Carbon;
use Closure;
use Illuminate\Http\Request;

class DingblockGateway
{
    /** @var int */
    private $startTime;

    /** @var Request */
    private $request;

    private $dingblockPubKey = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzMVET2BA/KRJXA/FqBYk
K2iT5pl39rfw3ZdeSZnaqeS3rZWiVqcWCeEj9CqHenB5TtgPhpyBmx24FVSmJ+zt
5R78Q10E7mJSPEeSQO7QUDABNeS18XomZx6JKfIe5k7J/Cgbr9wqQ5qc3sEReYz0
D2e1mmtWUQryHWhMgj75w85CcXpuSoCo9Zej+Mz9+puIQz0ZvFjVDabvJwMCeNR5
0F8xcvwfUvnmi9qw4xurbsToujoE9ukwkVQC7Na9979UE1KW2QRNhx8j1ze715qA
kZtJVxOo1WHLH9Q7TIbZRc3qejPCrjv0vRQWqGqXq265tob0CE96tJFGXeqojsWn
qQIDAQAB
-----END PUBLIC KEY-----';
    // 一级平台RSA私钥
    private $userPrivateKey = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJCbtgXezsd37WYTSfeMBKYT4jvqQ8H9Z7fO97ugsbqL/SJiGiolkosVZ4+STsVTJIqNjpXYXmwcbbSC1XmztK+RYmqD9bqUSccA6HYpycF2Jq9rohgnHWDnTK1Dy3Y2I2bJSj5tA13Rp9yt306BSRQGOiej30izKBgZseuvr1DmLpu/oPiTJC4N1ZWjMFj3j2dckD1OqW8TeXK0lpn/ZG9oP/1Jh5/K0Sl2ATzJLbYaNzQGWpiQDDmQXP7HShGy2cL3HkerFiEPwxNED49WY5g1tgDKkzLwMMmOYzBcivKjc8K10mzGm335Ixz/+3BCTn1GMth+Nc5kHWpykyxeC7AgMBAAECggEAO8GAyKwieEcslFxgX+Zsga9V4RoRhy2PezUvmf86vgXyvmxHIyhOiCHuKhckOns4bxCBKRx7aJPAn/1cPOYPQcG4K9Vmv8AYfMdyChvtoRfyUQj1WnCDlbTGUmOziG8qCjNMG0vIJ7qI3Usnnlw9gM86zli3gbC+WnBDw2agoaYV16IxiuArinrs50zHjCEFU8+4uv4rtvDe1nvRZFe0STXOMTplHkEMdNcLHi9VPUScxtBB8mij4wVe3CJydGVtd6HKFkL4aRbRhUzQ4Sv2UCMz6BKWEH1fr+Iqifd1HTXmJIz9tU5yFO8sPlROcz1g70LYeoo8lDKr6ZpGqTOIQQKBgQDz/6ACFrsA5S6qBsn4INA2hLzdwpBMb54Mo4cxUBO0lQqpKwcrIjtyfxZVBjX0OYgsiw9JdPsClZRYNP/AIgJ+Ftj/3AlPHOR8a2Of3zwQ1YWjdckY7qIy36UDlFyr4jJWflTNBVDDMQx6Wv+lPjAKKyemVcTgvPPa4gzZnsUorwKBgQDS7Shb4yAc1IRe0YriJiH18t1yGwOOob6/nnKu2+zds3iH/7BumIqGFvrktz3XjdmK8UW00vTw4oQTP9yjX/p84pnQO129ycHcBplaX0I5XwenJ+RWTH65FRAiYagtqnHkzz/FnRC84CvqWWHmhAcKEKBwwrMKorCxOopTvN7ztQKBgQCjOByRSSNPNgBBPMlecpTV43Vz4vMkLoZLbhjBpA+KfSjkgSQKdpvNu7X0qnLtLI8rPNq9CMPgtq7FUq7Cl/2S2zXN8iNICMvPbwb1qx9DI5KCBgi/5OIbpf98pYJYeLezpE6V1zhvWiA+uSDKKQ95veSIIFfS/pMhmJV//fVlgQKBgEs/N+qv+EDQv/hjoLxVBkCLx9xxLq+vnDEYAanH45Sj9hfua67I1IJJqG/3vyQrArTCpi+AIsncxWlomvndv6rV3DGhPCv80iAAaq0G1sVI1Z4YaSkwWPUGQp9BX5ILAF60YPfKtLHs9z2/bWGjMC+zBsmza9QbfhV74OVGt/MRAoGAL1KcT3eJ3RJpVtMU8A0SE0bNX3eYfL2WHX/GpPprVHY2NakxkwxnVJJb9SVsYV+mo8Nq18H2U6/7yoJ5cI4zt3K5tTRN5ojSKerrm/4SjOhsm9BpPMGxUx+6IRak85pHuuCWiE4y1WlNsWSJHD/Ma2egB8Ac2bwxM+kD06/0X3c=';

    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next)
    {
        $this->startTime = Carbon::now()->getTimestampMs();
        $this->request = $request;

        $appId = $request->route('agent_id');

        $agent = \Cache::remember('agent_app_' . $appId, 60 * 5, function () use ($appId) {
            return Agent::query()->where('app_id', $appId)->first();
        });
        if (!$agent) {
            return $this->failResponse('链接中包含无效的AppId');
        }

        //TODO: 检查参数签名
//        $this->verify($request);

        $request->merge([
            'agent_id' => $agent->id,
        ]);
        $resp = $next($request);
        $this->accessLog();

        return $resp;
    }

    public function verify($req)
    {
        $sign = $req->post('sign');

        if (empty($sign)) {
            return $this->failResponse(['result' => false, 'failedReason' => '缺少签名参数']);
        }

        // 组装签名字符串
        $arr = [];
        foreach ($req->post() as $name => $value) {
            if ('sign' != $name && 'agent_id' != $name) {
                array_push($arr, $name . '=' . $value);
            }
        }
        sort($arr);
        $signStr = join('&', $arr);

        $search = [
            '-----BEGIN PUBLIC KEY-----',
            '-----END PUBLIC KEY-----',
            "\n",
            "\r",
            "\r\n",
        ];
        //将pem格式密钥转换为openssl格式密钥
        $public_key = str_replace($search, '', $this->dingblockPubKey);
        $public_key = $search[0] . PHP_EOL . wordwrap($public_key, 64, "\n", true) . PHP_EOL . $search[1];
        $key = openssl_get_publickey($public_key);
        if (!$key) {
//            $this->output->writeln('【盯链】公钥无效');
            return $this->failResponse(['result' => false, 'failedReason' => '公钥无效']);
        }
        //调用openssl内置方法验签
        $result = (bool) openssl_verify($signStr, base64_decode($sign), $key, OPENSSL_ALGO_SHA256);
//        $this->output->writeln('【盯链】验签结果：' . ($result ? '成功' : '失败'));
        if (!$result) {
            return $this->failResponse(['result' => false, 'failedReason' => '验签失败']);
        }
    }

    private function failResponse($result)
    {
        $data = [];
        $data['code'] = 200;
        $data['msg'] = '响应成功';
        $data['nonce'] = md5(uniqid(microtime(true), true));
        $data['timestamp'] = time() * 1000;

        $bizData = [];
        $bizData['result'] = true;
        $data['bizData'] = json_encode($result);

        $sign = $this->sign($data);
        $data['sign'] = $sign;

        return $data;
    }

    /**
     * 加签方法.
     *
     * @param mixed $data
     */
    private function sign($data)
    {
        // 组装签名字符串
        $arr = [];
        foreach ($data as $name => $value) {
            // 如果bizData没有转成jsonstring
            if ('bizData' == $name && !is_string($value)) {
                return;
            }
            if ('sign' != $name) {
                array_push($arr, $name . '=' . $value);
            }
        }
        sort($arr);
        $signStr = join('&', $arr);

        $search = [
            '-----BEGIN RSA PRIVATE KEY-----',
            '-----END RSA PRIVATE KEY-----',
            "\n",
            "\r",
            "\r\n",
        ];
        $private_key = str_replace($search, '', $this->userPrivateKey);
        $private_key = $search[0] . PHP_EOL . wordwrap($private_key, 64, "\n", true) . PHP_EOL . $search[1];

        $res = openssl_get_privatekey($private_key);
        if ($res) {
            openssl_sign($signStr, $sign, $res, OPENSSL_ALGO_SHA256);
        } else {
            return -1;
        }

        return base64_encode($sign);
    }

    private function accessLog()
    {
        $request = $this->request;
        $cost = Carbon::now()->getTimestampMs() - $this->startTime;
        \Log::channel(Constant::LOG_CHANNEL_DINGBLOCK)->info(
            sprintf('[IN] %s:%s, COST: %sms', $request->getMethod(), $request->url(), $cost),
            [
                'content-type' => $request->header('Content-Type'),
                'query' => $request->query(),
                'post' => $request->all(),
            ]
        );
    }
}
